// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="UpdateWorkItem.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the UpdateWorkItem type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace ScrumForTeamSystem.EventService.AggregationService
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;

    using DataObjects;

    using Infrastructure;

    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    using Properties;

    /// <summary>
    /// Defines the UpdateWorkItem type.
    /// </summary>
    internal class UpdateWorkItem
    {
        /// <summary>
        /// Gets or sets the activite message.
        /// </summary>
        /// <value>The activite message.</value>
        public Action<string> ProcessMessage { get; set; }

        /// <summary>
        /// Executes the specified rules against the target work item.
        /// </summary>
        /// <param name="targetWorkItem">The target work item.</param>
        /// <param name="rules">The rules.</param>
        public void Execute(WorkItem targetWorkItem, IEnumerable<AggregateRule> rules)
        {
            var isSubscribed = this.ProcessMessage != null;

            AggregateRule lastRule = null;

            try
            {
                foreach (var rule in rules)
                {
                    lastRule = rule;

                    object existingValue, newValue;

                    if (!TryGetValues(targetWorkItem, rule, out existingValue, out newValue))
                    {
                        if (isSubscribed)
                        {
                            this.ProcessMessage("Unable to get the work item values. The field types may not be comparible to aggregate processing.");
                        }

                        continue;
                    }

                    if (!Equals(existingValue, newValue))
                    {
                        if (!targetWorkItem.IsOpen)
                        {
                            targetWorkItem.Open();
                        }

                        targetWorkItem.Fields[rule.Field.Target].Value = newValue;

                        if (!string.IsNullOrEmpty(rule.ChangeNote))
                        {
                            if (string.IsNullOrEmpty(targetWorkItem.History))
                            {
                                targetWorkItem.History = Settings.Default.ServiceName;
                            }

                            targetWorkItem.History = string.Concat(targetWorkItem.History, " - ", rule.ChangeNote);
                        }

                        if (isSubscribed)
                        {
                            this.ProcessMessage(
                                    string.Format(
                                            CultureInfo.InvariantCulture,
                                            "Work Item: '{0}' Updated. Field: '{1}' from '{2}' to '{3}'",
                                            targetWorkItem.Id,
                                            rule.Field.Target,
                                            existingValue,
                                            newValue));
                        }
                    }
                }

                if (targetWorkItem.IsDirty)
                {
                    targetWorkItem = GlobalHelpers.ValidateWorkItem(
                        GlobalHelpers.ApplyTimestamp(
                            targetWorkItem, Settings.Default.TimestampField));

                    targetWorkItem.Save();

                    if (isSubscribed)
                    {
                        this.ProcessMessage("Work item saved.");
                    }
                }
            }
            catch (Exception ex)
            {
                var message = string.Format(
                        CultureInfo.InvariantCulture,
                        Resources.String009,
                        targetWorkItem.Id);

                if (lastRule != null)
                {
                    message = string.Concat(message, Resources.String010, lastRule.Description);
                }

                throw new ServiceException(Settings.Default.ServiceName, message, ex);
            }
        }

        /// <summary>
        /// Tries the get the matching values.
        /// </summary>
        /// <param name="targetWorkItem">The target work item.</param>
        /// <param name="rule">The aggregate rule.</param>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns>
        /// <c>True</c> is the values can be calculated; otherwise <c>false</c>.
        /// </returns>
        private static bool TryGetValues(WorkItem targetWorkItem, AggregateRule rule, out object currentValue, out object newValue)
        {
            RuleFunctions functions;

            if (!RuleCacheHelper.TryGetRuleFunctions(rule, targetWorkItem, out functions))
            {
                currentValue = null;
                newValue = null;

                return false;
            }

            currentValue = functions.CurrentValue.Invoke(targetWorkItem);
            newValue = functions.NewValue.Invoke(targetWorkItem.GetLinkedSources(rule));

            return true;
        }
    }
}